package internal

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/coocood/freecache"

	oneProto "go.opentelemetry.io/collector/pkg/translator/oneapm/proto"
)

func GetCommonMetricName(span *oneProto.SpanInfo, transData *oneProto.TransactionDataRequest, cache *freecache.Cache) string {
	var metricName string
	if Message == span.GetType() {
		metricName = getMetricNameMq(span)
	} else if External == span.GetType() {
		metricName = getMetricNameExternal(span, transData)
	} else if DataStoreMongodb == span.GetKind() || DataStoreCassandra == span.GetKind() || DataStoreInfluxDB == span.GetKind() {
		metricName, _, _ = getMetricNameMongodbOrCassandraOrInfluxDB(span, transData)
	} else if DataStoreRedis == span.GetKind() || DataStoreMemcached == span.GetKind() || DataStorexMemcached == span.GetKind() || DataStorespyMemcached == span.GetKind() {
		metricName, _ = getMetricNameRedisOrMemcached(span, transData)
	} else if Database == span.GetType() { // database
		metricName, _, _ = getMetricNameDatabase(span, transData, cache)
	} else {
		metricName = getOtherMetricName(span, transData)
	}
	return metricName
}

func GetMethod(method int32) string {
	s := method
	if 1 == s {
		return "GET"
	} else if 2 == s {
		return "POST"
	} else if 3 == s {
		return "HEAD"
	} else if 4 == s {
		return "PUT"
	} else if 5 == s {
		return "DELETE"
	} else if 6 == s {
		return "CONNECT"
	} else if 7 == s {
		return "OPTIONS"
	} else if 8 == s {
		return "TRACE"
	} else {
		return ""
	}
}

func getMetricNameMq(span *oneProto.SpanInfo) string {
	MetricNameStrArr := []string{MessageBroker, KindInfo[span.GetKind()]["oneapm"], MQTypeInfo[span.GetMq().GetMqType()], MQDestType[span.GetMq().GetDestType()]}
	if span.GetMq().GetDestName() != "" {
		MetricNameStrArr = append(MetricNameStrArr, span.GetMq().GetDestName())
	}
	if span.GetMq().GetName() != "" {
		MetricNameStrArr = append(MetricNameStrArr, span.GetMq().GetName())
	}
	metricName := JoinStrings(MetricNameStrArr, "/")
	return metricName
}

func getMetricNameExternal(span *oneProto.SpanInfo, transData *oneProto.TransactionDataRequest) string {
	kindType := KindInfo[span.GetKind()]["oneapm"]
	host := GetHost(span.GetExternal().GetUrl(), span.GetExternal().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
	var hostAndPort string
	if span.GetExternal().GetPort() != "" {
		hostAndPort = JoinStrings([]string{host, span.GetExternal().GetPort()}, ":")
	} else {
		hostAndPort = host
	}
	operate := span.GetExternal().GetUri() // 除了Http外都会有这个数据，由className与methodName组成
	operate = strings.TrimLeft(operate, "/")
	if "http" == strings.ToLower(kindType) {
		operate = getHTTPSegment(operate)
		if operate != "" {
			return JoinStrings([]string{ExternalName, kindType, hostAndPort, operate}, "/")
		} else {
			return JoinStrings([]string{ExternalName, kindType, hostAndPort}, "/")
		}
	}
	return JoinStrings([]string{ExternalName, kindType, operate}, "/")
}

// 通过uri获取Http请求的前两段
func getHTTPSegment(uri string) string {
	if "" == uri {
		return ""
	}
	uriPrefix := ""
	if strings.Contains(uri, "?") {
		uriPrefix = uri[0:strings.Index(uri, "?")]
	} else if strings.Contains(uri, "#") {
		uriPrefix = uri[0:strings.Index(uri, "#")]
	} else {
		uriPrefix = uri
	}
	return uriPrefix
}

func getMetricNameMongodbOrCassandraOrInfluxDB(span *oneProto.SpanInfo, transData *oneProto.TransactionDataRequest) (string, string, string) {
	var table, operate, rootName, connStr, host, port string
	if DataStoreMongodb == span.GetKind() {
		table = span.MongodbSpanInfo.Dborcoll
		if 0 < span.MongodbSpanInfo.Operation {
			if int(span.MongodbSpanInfo.Operation) < len(MongodbOpArray) {
				operate = MongodbOpArray[span.MongodbSpanInfo.Operation]
			}
		}
		host = GetHostFromIndex(span.GetMongodbSpanInfo().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
		port = strconv.Itoa(int(span.GetMongodbSpanInfo().GetPort()))
	} else {
		if DataStoreCassandra == span.GetKind() {
			host = GetHostFromIndex(span.GetCassandraSpanInfo().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			port = strconv.Itoa(int(span.GetCassandraSpanInfo().GetPort()))
			table, operate = getTableAndOpCassandra(span)
		} else {
			host = GetHostFromIndex(span.GetInfluxDBSpanInfo().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			port = strconv.Itoa(int(span.GetInfluxDBSpanInfo().GetPort()))
			table, operate = getTableAndOpInfluxd(span)
		}
	}
	if port != "0" {
		connStr = JoinStrings([]string{KindInfo[span.GetKind()]["oneapm"], host, port}, ":")
	} else {
		connStr = JoinStrings([]string{KindInfo[span.GetKind()]["oneapm"], host}, ":")
	}

	rootName = JoinStrings([]string{DatastoreStatement, connStr, table, operate}, "/")
	return rootName, connStr, operate
}

func getMetricNameRedisOrMemcached(span *oneProto.SpanInfo, transData *oneProto.TransactionDataRequest) (string, string) {
	var operate, rootName, host, port, connStr string
	if KindInfo[span.GetKind()]["oneapm"] == "Redis" {
		port = "6379"
		if span.RedisSpanInfo.GetRedisOperation() > 0 && span.RedisSpanInfo.GetRedisOperation() < int32(len(RedisOpArr)) {
			operate = RedisOpArr[span.RedisSpanInfo.GetRedisOperation()]
		}
		if 0 < span.GetRedisSpanInfo().GetHostport() {
			hostPort := GetHostFromIndex(span.GetRedisSpanInfo().GetHostport(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			index := strings.Index(hostPort, ":")
			if -1 != index {
				host = hostPort[0:index]
				port = hostPort[index+1:]
			}
		} else {
			host = GetHostFromIndex(span.GetRedisSpanInfo().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			port = fmt.Sprint(span.GetRedisSpanInfo().GetPort())
		}
	} else if KindInfo[span.GetKind()]["oneapm"] == "Memcached" {
		port = "11211"
		operateIndex := int(span.GetMemcachedSpanInfo().GetOperation() - 1)
		if operateIndex >= 0 && operateIndex < len(Memcached) {
			operate = Memcached[operateIndex]
		}
		if 0 < span.GetMemcachedSpanInfo().GetHostport() {
			hostPort := GetHostFromIndex(span.GetMemcachedSpanInfo().GetHostport(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			index := strings.Index(hostPort, ":")
			if -1 != index {
				host = hostPort[0:index]
				port = hostPort[index+1:]
			}
		}
	} else if KindInfo[span.GetKind()]["oneapm"] == "xMemcached" {
		operateIndex := int(span.GetXmemcachedSpanInfo().GetOperation() - 1)
		if operateIndex >= 0 && operateIndex < len(Memcached) {
			operate = Memcached[operateIndex]
		}
		if 0 < span.GetXmemcachedSpanInfo().GetHost() {
			host = GetHostFromIndex(span.GetXmemcachedSpanInfo().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			port = fmt.Sprint(span.GetXmemcachedSpanInfo().GetPort())
		}
	} else if KindInfo[span.GetKind()]["oneapm"] == "spymemcached" {
		if 0 < span.GetSpymemcachedSpanInfo().GetHost() {
			host = GetHostFromIndex(span.GetSpymemcachedSpanInfo().GetHost(), transData.TransactionInfo.GetDataContext().HostAndUrls)
			port = fmt.Sprint(span.GetSpymemcachedSpanInfo().GetPort())
		}
	}
	for _, value := range span.AgentAttributes {
		if value.GetKey() == "db.operation" && value.GetValue() != "" {
			operate = value.GetValue()
		}
	}
	if port != "0" {
		connStr = JoinStrings([]string{KindInfo[span.GetKind()]["oneapm"], host, port}, ":")
	} else {
		connStr = JoinStrings([]string{KindInfo[span.GetKind()]["oneapm"], host}, ":")
	}
	if operate != "" {
		rootName = JoinStrings([]string{DatastoreOperation, connStr, operate}, "/")
	} else {
		rootName = JoinStrings([]string{DatastoreOperation, connStr}, "/")
	}
	return rootName, connStr
}

func getMetricNameDatabase(span *oneProto.SpanInfo, transData *oneProto.TransactionDataRequest, cache *freecache.Cache) (string, string, string) {
	var operate, table, db string
	if _, ok := KindInfo[span.GetKind()]; !ok {
		return "", "", operate
	}
	var rootName, connStr string
	sql := span.GetDatabaseSpanInfo().GetSql()
	table, operate = getTableAndOpFromDatabaseSQL(sql, cache)
	for _, value := range span.AgentAttributes {
		if value.GetKey() == "db.sql.table" {
			table = value.GetValue()
		} else if value.GetKey() == "db.operation" {
			operate = value.GetValue()
		}
	}
	_, host, port, db := getDatabase(span, KindInfo[span.GetKind()]["oneapm"], transData.TransactionInfo.GetDataContext().GetHostAndUrls(), transData.Identifier, cache)
	connStr = JoinStrings([]string{KindInfo[span.GetKind()]["oneapm"], host, port}, ":")
	rootName = JoinStrings([]string{DatabaseStatement, connStr, db, table, operate}, "/")
	return rootName, connStr, operate
}

func getOtherMetricName(value *oneProto.SpanInfo, transData *oneProto.TransactionDataRequest) string {
	var name string
	var kind string
	if value.GetCommonMetricSpanInfo().GetMetricPrefix() > 0 && value.GetCommonMetricSpanInfo().GetMetricPrefix() <= int32(len(CommonMetricKindInfo)) {
		kind = CommonMetricKindInfo[value.GetCommonMetricSpanInfo().GetMetricPrefix()-1]
	} else {
		language := strings.Split(transData.Identifier, ":")
		kind = language[0]
	}

	if value.CommonMetricSpanInfo.GetMetricName() != "" {
		name = JoinStrings([]string{kind, value.CommonMetricSpanInfo.GetMetricName()}, "/")
	} else if value.CommonMetricSpanInfo.GetClassName() != "" && value.CommonMetricSpanInfo.GetMethodName() != "" {
		name = JoinStrings([]string{kind, value.CommonMetricSpanInfo.GetClassName(), value.CommonMetricSpanInfo.GetMethodName()}, "/")
	} else {
		var methodName string
		if value.GetMethodNameIndex() > 0 {
			if _, ok := MethodIndexName[value.GetMethodNameIndex()]; ok {
				methodName = MethodIndexName[value.GetMethodNameIndex()]
			}
		} else {
			methodName = value.GetMethodName()
		}
		if value.GetClassName() != "" && methodName != "" {
			name = JoinStrings([]string{kind, value.GetClassName(), methodName}, "/")
		} else if methodName != "" {
			name = JoinStrings([]string{kind, methodName}, "/")
		}
	}
	return name
}

func getTableAndOpCassandra(span *oneProto.SpanInfo) (string, string) {
	query := span.GetCassandraSpanInfo().GetSql()
	patterns := []string{CqlSelectPatternCassandra, CqlUpdatePatternCassandra, CqlInsertPatternCassandra, CqlDeletePatternCassandra}
	tableIndex := []int{2, 1, 1, 3}
	operations := []string{"select", "update", "insert", "delete"}
	for i := 0; i < len(patterns); i++ {
		tablePos := tableIndex[i]
		reg := regexp.MustCompile(patterns[i])
		result := reg.FindStringSubmatch(query)
		if tablePos >= 0 && tablePos < len(result) {
			if "" != result[tablePos] {
				return result[tablePos], operations[i]
			}
		}
	}
	return "", ""
}

func getTableAndOpInfluxd(span *oneProto.SpanInfo) (string, string) {
	query := span.GetInfluxDBSpanInfo().GetSql()
	patterns := []string{CqlSelectPatternInfluxdb, CqlInsertPatternInfluxdb, CqlDeletePatternInfluxdb}
	tableIndex := []int{2, 1, 3}
	operations := []string{"select", "insert", "delete"}
	for i := 0; i < len(patterns); i++ {
		tablePos := tableIndex[i]
		reg := regexp.MustCompile(patterns[i])
		result := reg.FindStringSubmatch(query)
		if tablePos >= 0 && tablePos < len(result) && "" != result[tablePos] {
			return result[tablePos], operations[i]
		}
	}
	return "", ""
}

// 根据url中自动识别出来type host port database
func getDatabase(span *oneProto.SpanInfo, kind string, hostAndUrls []string, identifier string, cache *freecache.Cache) (string, string, string, string) {
	url := GetHostFromIndex(span.GetDatabaseSpanInfo().GetUrl(), hostAndUrls)
	if "" != strings.Trim(url, " ") {
		// 如果是java探针，走原来逻辑，否则走通用逻辑
		language := strings.Split(identifier, ":")[0]
		if strings.ToLower(language) != "java" {
			arr := strings.Split(url, "/")
			hostAndPort := strings.Split(arr[0], ":")
			var db string
			if len(arr) >= 2 {
				db = arr[1]
			}
			var port string
			if len(hostAndPort) >= 2 {
				port = hostAndPort[1]
			}
			return kind, hostAndPort[0], port, db
		}
		sqlYrlEntity := generateEntity(url, kind, cache)
		if nil != sqlYrlEntity {
			return sqlYrlEntity.Type, sqlYrlEntity.Host, sqlYrlEntity.Port, sqlYrlEntity.Database
		}
	}
	url = GetHostFromIndex(span.GetDatabaseSpanInfo().GetUrlFromDataSource(), hostAndUrls)
	if "" != url {
		url = getJdbcURL(url)
		urlData := strings.Split(url, ":")
		if len(urlData) == 4 {
			if "sqlserver" == strings.ToLower(urlData[0]) {
				urlData[0] = "mssql"
			}
			return urlData[0], urlData[1], urlData[2], urlData[3]
		}
	}
	return "", "", "", ""
}
